﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using Krilib.Data;
using Krilib.Control.Base;
using Krile.Data;

namespace Krile.Forms.MainForm.Controls.Timeline
{
    public partial class TimelineList : Control
    {
        #region Properties

        private object statusLocker = new object();
        private XStatus[] statuses;
        internal XStatus[] Statuses
        {
            get
            {
                return statuses;
            }
            set
            {
                if (statuses == null || value == null || value.Length < statuses.Length)
                    SelectedIndex = -1;
                int prevlen = statuses != null ? statuses.Length : 0;
                lock (statusLocker)
                {
                    statuses = value;
                }
                if (selectedIndex != -1 &&
                    statuses != null && statuses.Length > prevlen &&
                    !Core.Config.Timeline.ListBottomNewer)
                    selectedIndex = statuses.Length - prevlen + selectedIndex;
            }
        }

        internal void StatusUpdateProcess()
        {
            if (StatusesChanged != null)
                StatusesChanged.Invoke(this, new EventArgs());
            remeasureRequired = true;
            this.Refresh();
            TimelineMouseControllableItemsUpdatedInvoke();
        }

        private readonly TimelineListRenderer renderer;

        bool remeasureRequired = false;

        protected override void OnSizeChanged(EventArgs e)
        {
            base.OnSizeChanged(e);
            if (ScrollChanged != null)
                ScrollChanged.Invoke(this, new EventArgs());
        }

        private int _selectedIndex = -1;

        /// <summary>
        /// for internal control
        /// </summary>
        private int selectedIndex
        {
            get { return _selectedIndex; }
            set
            {
                releaseSelectedItemResource();
                _selectedIndex = value;
                remeasureRequired = true;
                if (SelectedChange != null)
                    SelectedChange.Invoke(this, new EventArgs());
            }
        }

        /// <summary>
        /// Selected item index
        /// </summary>
        public int SelectedIndex
        {
            get { return selectedIndex; }
            set
            {
                selectedIndex = value;
                renderer.UpdateMeasuring(value);
                Refresh();
                TimelineMouseControllableItemsUpdatedInvoke();
            }
        }

        /// <summary>
        /// Selected status object
        /// </summary>
        internal XStatus SelectedStatus
        {
            get
            {
                if (selectedIndex == -1 || !IsListHasItem)
                    return null;
                else
                    return statuses[selectedIndex];
            }
        }

        int verticalPosition = 0;
        public int VerticalPosition
        {
            get { return verticalPosition > VPosMax ? VPosMax : verticalPosition; }
            set
            {
                verticalPosition = value;
                if (ScrollChanged != null)
                    ScrollChanged.Invoke(this, new EventArgs());
                Refresh();
                TimelineMouseControllableItemsUpdatedInvoke();
            }
        }

        internal TimelineMouseControllableItem[] MouseItems
        {
            get
            {
                return renderer.Items;
            }
        }

        public int VPosMax
        {
            get
            {
                if (remeasureRequired)
                    Remeasure();
                int vpm = renderer.ListHeight - this.Height;
                return vpm < 0 ? 0 : vpm;
            }
        }

        #endregion

        #region Events

        public event EventHandler SelectedChange;

        public event EventHandler ScrollChanged;

        public event EventHandler StatusesChanged;

        internal event EventHandler TimelineMouseControllableItemsUpdated;
        internal void TimelineMouseControllableItemsUpdatedInvoke()
        {
            if (TimelineMouseControllableItemsUpdated != null)
                TimelineMouseControllableItemsUpdated.Invoke(this, new EventArgs());
        }

        #endregion

        #region Public control

        public bool SelectIndexByUID(ulong uid)
        {
            if(selectIndexByUID(uid))
            {
                EnsureVisible();
                FullRedraw();
                return true;
            }
            return false;
        }

        public void EnsureVisible()
        {
            int visiblePoint = renderer.NormalItemHeight * selectedIndex;
            if (this.verticalPosition > visiblePoint)
                this.verticalPosition = visiblePoint;
            else if (this.verticalPosition + this.Height < visiblePoint + renderer.SelectedItemHeight)
                this.verticalPosition = visiblePoint - this.Height + renderer.SelectedItemHeight;
        }

        #endregion

        #region Internal control

        private bool IsListHasItem
        {
            get
            {
                return statuses != null && statuses.Length != 0;
            }
        }

        private bool selectIndexByUID(ulong uid)
        {
            for(int idx = 0;idx < statuses.Length ; idx++)
            {
                if (statuses[idx].BaseStatus.UID == uid)
                {
                    _selectedIndex = idx;
                    return true;
                }
            }
            return false;
        }

        private void releaseSelectedItemResource()
        {
            if (SelectedStatus != null)
                foreach (var msgobj in SelectedStatus.BaseStatus.text.MessageObjects)
                    msgobj.Reset();
        }

        #endregion

        public TimelineList()
        {
            SetStyle(ControlStyles.AllPaintingInWmPaint, true);
            SetStyle(ControlStyles.Opaque, true);
            SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
            SetStyle(ControlStyles.Selectable, true);
            SetStyle(ControlStyles.UserMouse, true);
            SetStyle(ControlStyles.UserPaint, true);
            SetStyle(ControlStyles.ResizeRedraw, true);
            InitializeComponent();
            renderer = new TimelineListRenderer(this);
        }

        public void FullRedraw()
        {
            remeasureRequired = true;
            this.Refresh();
        }
        
        #region Override methods

        volatile bool _refreshing;
        volatile bool _reqrefresh;
        public override void Refresh()
        {
            if (_refreshing)
            {
                _reqrefresh = true;
                return;
            }
            if (InvokeRequired)
            {
                //for freeze prevention
                this.Invoke(new MethodInvoker(this.Refresh));
            }
            else
            {
                if (_refreshing)
                {
                    _reqrefresh = true;
                    return;
                }
                else
                {
                    _refreshing = true;
                    do
                    {
                        _reqrefresh = false;
                        base.Refresh();
                        Application.DoEvents();
                    }
                    while (_reqrefresh);
                    _refreshing = false;
                }
            }
        }

        public bool Remeasure()
        {
            if (remeasureRequired)
            {
                remeasureRequired = false;
                renderer.UpdateMeasuring(selectedIndex);
                return true;
            }
            return false;
        }

        protected override void OnResize(EventArgs e)
        {
            remeasureRequired = true;
            if (ScrollChanged != null)
                ScrollChanged.Invoke(this, new EventArgs());
            base.OnResize(e);
        }

        protected override void OnPaint(PaintEventArgs pe)
        {
            bool raiseMouseUpdated = Remeasure();

            if (!IsListHasItem)
            {
                pe.Graphics.FillRectangle(SystemBrushes.Control, ClientRectangle);
                TextRenderer.DrawText(pe.Graphics, Lang.Main.Timeline_ListNoStatus,
                    this.Font, this.ClientRectangle, SystemColors.GrayText,
                    TextFormatFlags.EndEllipsis | TextFormatFlags.HorizontalCenter | TextFormatFlags.NoPrefix
                    | TextFormatFlags.SingleLine | TextFormatFlags.VerticalCenter);
                return;
            }
            pe.Graphics.FillRectangle(SystemBrushes.Control, pe.ClipRectangle);
            int vpos = -verticalPosition;

            //lock for prevention interrupt changing
            //(it will cause access error)
            lock (statusLocker)
            {
                for (int i = 0; i < statuses.Length; i++)
                {
                    if (i == selectedIndex)
                    {
                        statuses[i].Unread = false;

                        if (vpos + renderer.SelectedItemHeight > 0)
                            renderer.DrawSelectedItem(pe.Graphics, i, vpos, this.Width);
                        vpos += renderer.SelectedItemHeight;
                    }
                    else
                    {
                        if (vpos + renderer.NormalItemHeight > 0)
                            renderer.DrawNormalItem(pe.Graphics, i, vpos, this.Width);
                        vpos += renderer.NormalItemHeight;
                    }
                    if (vpos > this.Height) break;
                }
            }

            //add for verticalposition
            vpos += verticalPosition;
            if (vpos < ClientRectangle.Height)
            {
                Rectangle remain = new Rectangle(0, vpos, ClientRectangle.Width, ClientRectangle.Height - vpos);
                pe.Graphics.FillRectangle(SystemBrushes.Control, remain);
            }

            if(raiseMouseUpdated)
                TimelineMouseControllableItemsUpdatedInvoke();
        }

        #endregion
    }
}
